In the previous article, we looked at how C# type conversion works on basic types like String and Generics. Let’s deep dive into how Covariance works on in and out modifiers.
Have you noticed anything when using a generic collection or a delegate? There are in and out modifiers at the beginning of the accepted types. For example, IEnumerable<T>
contains the out modifier in the source code.
Summary:
Exposes the enumerator, which supports a simple iteration over a collection of a specified type.
Type parameters:
T: The type of objects to enumerate.
1 2 3 4
public interface IEnumerable < [NullableAttribute(2)] out T >: IEnumerable { IEnumerable < T > GetEnumerator(); }
Or we often use the delegate Func<> source code in and out. For example, the source code for a Func that accepts 5 types is as follows.
1 2 3
namespace System { public delegate TResult Func < in T1, in T2, in T3, in T4, out TResult > (T1 arg1, T2 arg2, T3 arg3, T4 arg4); }
After C# 4.0 for Generic Interface and Delegate, the in and out modifier entered our lives. What if I tell you that we use Covariance property on out type and Contravariance feature when we use in? It’s a mess, isn’t it? While trying to understand the features of invariance, Covariance, and Contravariance, we come across in and out. Relax and sit back. When this episode is over, everything will be in place :)
When using in and out, we restrict the methods defined in the relevant interface. With these restrictions, we can easily switch between types. So we are able to work type-safe.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class Animal {} class Mammal: Animal {} // Every mammal is an animal. class Dog: Mammal {} // Every Dog is an mammal. interface IInvariant < T > { T Get(); //Invariant type can be used as return type. void Set(T t); // Invariant type can be used as parameter. } interface ICovariant < out T > { T Get(); // Covariant type can be used as return type. //void Set(T t); // Covariant type cannot be used as parameter! ,compilation error } interface IContravariant < in T > { //T Get(); // Contravariant type cannot be used as return type., compilation error void Set(T t); // Contravariant type can be used as parameter. }
Why does the generic interface, which does not take any modifier as a type, have only invariant property? For example, why do we get an error when we run code like the following?
1 2
IInvariant < object > invariantMammal = (IInvariant < string > ) null; // compilation error IInvariant < string > invariantMammal = (IInvariant < ok > ) null; // compilation error
When we try to apply covariance or contravariance, we get a compilation error. Let’s examine it step by step.
1 2 3 4
If we apply a covariant approach; // Mammal less derived Dog more derived. less derived <== more derived IInvariant < Mammal > invariantMammal = (IInvariant < Dog > ) null; // compilation error
We get a compilation error. But why?IInvariant<Mammal>
.Get() expects an object with a return type of Mammal. IInvariant<Dog>
.Get() returns Dog as the return type. Since all dogs are mammals, they are suitable for use in this way.IInvariant<Mammal>
.Set(Mammal) expects Mammal as the parameter. IInvariant<Dog>
.Set(Dog) Accepts all objects of the Dog type since we have assigned. This is not suitable for use as all mammals are not dogs.
If we apply the contravariant approach:
1
2
// Mammal more derived Animal less derived. more derived <== less derived
IInvariant < Mammal > invariantMammal = (IInvariant < Animal > ) null; // compilation error
We get a compilation error. But why?
IInvariant<Mammal>
.Get() expects an object with a return type of Mammal. But IInvariant<Animal>
.Get() returns Animal as the return type. Since all animals are not mammals, they are not suitable for use in this way.
IInvariant<Mammal>
.Set(Mammal) expects Mammal as the parameter. IInvariant<Animal>
.Set(Animal) accepts all objects of type Animal since we have assigned them as. Mammal is also suitable for use in this way because they are of Animal type.
1 2 3 4 5 6 7 8 9 10 11
IInvariant < Animal > invariantAnimal1 = (IInvariant < Animal > ) null; //IInvariant<Animal> invariantAnimal2 = (IInvariant<Mammal>)null; // compilation error //IInvariant<Animal> invariantAnimal3 = (IInvariant<Dog>)null; // compilation error //IInvariant<Mammal> invariantMammal1 = (IInvariant<Animal>)null; // compilation error IInvariant < Mammal > invariantMammal2 = (IInvariant < Mammal > ) null; //IInvariant<Mammal> invariantMammal3 = (IInvariant<Dog>)null; // compilation error //IInvariant<Dog> invariantDog1 = (IInvariant<Animal>)null; // compilation error //IInvariant<Dog> invariantDog2 = (IInvariant<Mammal>)null; // compilation error IInvariant < Dog > invariantDog3 = (IInvariant < Dog > ) null;
In short, for an interface that takes invariant as a parameter, the types must be the same. So more derived and less derived types are not allowed.
What happens if we use ICovariant<out T>
?
The return type of the methods described in the interface can be T.
In the methods defined in the interface, type T cannot be given as a parameter.
1
2
// Mammal more derived Animal less derived. more derived <== less derived
ICovariant < Mammal > covariantMammal = (ICovariant < Animal > ) null; //compilation error
We get a compilation error. But why?
ICovariant<Mammal>
.Get() expects an object with a fallback type of Mammal. But ICovariant<Animal>
.Get() returns Animal as the return type. Since all animals are not mammals, they are not suitable for use in this way.
We cannot use ICovariant.Set(Mammal) anyway. We have implemented this restriction with the Out modifier.
1
2
// Mammal less derived Dog more derived. less derived <== more derived
ICovariant < Mammal > covariantMammal = (ICovariant < Dog > ) null; //OK
We were able to make a definition like this. But why?ICovariant<Mammal>
.Get() expects an object with a fallback type of Mammal. ICovariant<Dog>
.Get() returns Dog as the return type. Since all dogs are mammals, they are suitable for use in this way.
We cannot use ICovariant.Set(Mammal) anyway. We have implemented this restriction with the Out modifier.
1 2 3 4 5 6 7 8 9 10 11
ICovariant < Animal > covariantAnimal1 = (ICovariant < Animal > ) null; ICovariant < Animal > covariantAnimal2 = (ICovariant < Mammal > ) null; ICovariant < Animal > covariantAnimal3 = (ICovariant < Dog > ) null; //ICovariant<Mammal> covariantMammal1 = (ICovariant<Animal>)null; // compilation error ICovariant < Mammal > covariantMammal2 = (ICovariant < Mammal > ) null; ICovariant < Mammal > covariantMammal3 = (ICovariant < Dog > ) null; //ICovariant<Dog> covariantDog1 = (ICovariant<Animal>)null; // compilation error //ICovariant<Dog> covariantDog2 = (ICovariant<Mammal>)null; // compilation error ICovariant < Dog > covariantDog3 = (ICovariant < Dog > ) null;
In short, less derived <== more derived can be assigned as a parameter for a covariant (out) type. More derived <== less derived cannot be assigned. In other words, when we use out we gain Covariance features and we exclude Contravariance features. In this way, we work in a type-safe way.
What happens if we use IContravariant <in T>
?
The return type of the methods defined in the interface cannot be T (In use restriction) Restriction
In the methods defined in the interface, type T can be given as a parameter.
1
2
// Mammal more derived Animal less derived. more derived <== less derived
IContravariant < Mammal > contravariantMammal = (IContravariant < Animal > ) null;
We could make a definition like this, but why?
IContravariant<Mammal>
.Set(Mammal) expects Mammal as the parameter. IContravariant<Animal>
.Set(Animal) accepts all objects of type Animal since we have assigned them as Mammal is also suitable for use in this way because they are of Animal type.
We can’t use. IContravariant<Mammal>
.Get() anyway. We have implemented this restriction within the modifier.
1
2
// Mammal less derived Dog more derived. less derived <== more derived
IContravariant < Mammal > contravariantMammal = (IContravariant < Dog > ) null; // compilation error
We got a compilation error. But why?
We can’t use IContravariant<Mammal>
.Get() anyway. We have implemented this restriction within the modifier.IContravariant<Mammal>
.Set(Mammal) expects Mammal as the parameter. IContravariant<Dog>
.Set(Dog) Accepts all objects of the Dog type since we have assigned. This is not suitable for use as all mammals are not dogs.
1 2 3 4 5 6 7 8 9 10 11
IContravariant < Animal > contravariantAnimal1 = (IContravariant < Animal > ) null; //IContravariant<Animal> contravariantAnimal2 = (IContravariant<Mammal>)null; // compilation error //IContravariant<Animal> contravariantAnimal3 = (IContravariant<Dog>)null; // compilation error IContravariant < Mammal > contravariantMammal1 = (IContravariant < Animal > ) null; IContravariant < Mammal > contravariantMammal2 = (IContravariant < Mammal > ) null; //IContravariant<Mammal> contravariantMammal3 = (IContravariant<Dog>)null; // compilation error IContravariant < Dog > contravariantDog1 = (IContravariant < Animal > ) null; IContravariant < Dog > contravariantDog2 = (IContravariant < Mammal > ) null; IContravariant < Dog > contravariantDog3 = (IContravariant < Dog > ) null;
In short, more derived <== less derived can be assigned as a parameter for a contravariant (in) type. Less derived <== more derived cannot be assigned. In other words, we have excluded the Covariance property while gaining Contravariance property. In this way, we work in a type-safe way.
I’ve made a uniform for the generic interface to make it easy to understand the examples. But be aware that we can add properties for multiple generic types separately. For example, we could make use of the following.
1 2 3
interface IExample < out T1, in T2, out T3, T4, in T5 > { //... }
This is the end of the article. I Hope it helped you to understand the concept.
Stackoverflow page: https://stackoverflow.com/a/53134957/8069766
C# | Object Class. https://www.geeksforgeeks.org/c-sharp-object-class/
Covariance and Contravariance (C#). https://docs.microsoft.com/tr-tr/dotnet/csharp/programming-guide/concepts/covariance-contravariance/
Covariance and Contravariance in Generics. https://docs.microsoft.com/tr-tr/dotnet/standard/generics/covariance-and-contravariance